GRAY_CNN(图片灰度化处理+图片识别)

应用概述

这里将以图片灰度化加图片识别来介绍AI+DSP应用开发的开发流程。 其中图片灰度化可以使用DSP来完成,图片识别则使用AI来完成。 灰度化处理使用灰度化公式来进行,图片识别使用CNN模型。

开发流程

1. 定义模型

  • 图片灰度化处理过程。

这里实现图片灰度化,使用蓝、绿、红三个通道的值进行加权求和,计算出一个灰度值。 这里使用的权重分别是0.114、0.587和0.299,这些数值是基于人眼对不同颜色的敏感度来选择的, 用于将彩色图像转换为灰度图像。公式为:

\[Gray = B \times 0.114 + G \times 0.587 + R \times 0.299\]

以及定义一个Clip操作,确保灰度值在0到255之间。 代码示例如下:

 1import mindspore as ms
 2from mindspore.train.serialization import export
 3import numpy as np
 4from mindspore import nn, ops
 5
 6class Gray(nn.Cell):
 7    def __init__(self):
 8        super(Gray, self).__init__()
 9        self.split = ops.Split(axis=2, output_num=3)
10    def construct(self, x):
11        x = self.split(x)
12        b, g, r = x
13        x = (
14            b * 0.114 +
15            g * 0.587 +
16            r * 0.299
17        ).squeeze(-1)
18        x = ops.clip_by_value(x, 0, 255)
19        return x
  • 定义AI模型,用于图片识别。

这里的AI模型是卷积神经网络(CNN)。CNN主要由卷积,池化,激活等算子组成; CNN模型架构如下表所示:

层级

操作类型

参数细节

输出维度

输入层

灰度图像输入

1通道,尺寸 H×W

H×W×1

卷积块1

Conv2d

输入通道:1 → 输出通道:32, 卷积核:3×3

H×W×32

ReLU激活

非线性变换

H×W×32

MaxPool2d

窗口:2×2, 步长:2

H/2×W/2×32

卷积块2

Conv2d

输入通道:32 → 输出通道:64, 卷积核:3×3

H/2×W/2×64

ReLU激活

非线性变换

H/2×W/2×64

MaxPool2d

窗口:2×2, 步长:2

H/4×W/4×64

卷积块3

Conv2d

输入通道:64 → 输出通道:128, 卷积核:3×3

H/4×W/4×128

ReLU激活

非线性变换

H/4×W/4×128

MaxPool2d

窗口:2×2, 步长:2

H/8×W/8×128

全连接层

Flatten

展平多维特征图

32768 (H/8×W/8×128)

Dense

输入:32768 → 输出:64, 激活:ReLU

64

Dense (输出层)

输入:64 → 输出:5, 无激活

5

输出层

分类结果

5类概率分布

5

CNN模型用于图片识别,不能直接导出模型,需要训练模型,该部分内容见: 2. 模型训练

定义CNN模型的代码示例如下:

 1import mindspore as ms
 2from mindspore import nn
 3class CNN(nn.Cell):
 4def __init__(self):
 5    super().__init__()
 6    self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True)
 7    self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
 8    self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True)
 9    self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
10    self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True)
11    self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
12    self.flatten = nn.Flatten()
13    self.dense1 = nn.Dense(32768, 64)
14    self.dense2 = nn.Dense(64, 5)
15    self.relu = nn.ReLU()
16
17def construct(self, x):
18    x = self.relu(self.conv1(x))
19    x = self.pool1(x)
20    x = self.relu(self.conv2(x))
21    x = self.pool2(x)
22    x = self.relu(self.conv3(x))
23    x = self.pool3(x)
24    x = self.flatten(x)
25    x = self.relu(self.dense1(x))
26    x = self.dense2(x)
27    return x

2. 模型训练

CNN模型需要进行训练才能正确识别,训练需要数据集,这里已经提前准备好数据集,放在Target文件夹下, 使用MindSpore框架进行模型训练,需要导入相关库和模块,定义数据预处理、模型结构、损失函数和优化器等。 重新组网时,直接使用 nn.GraphCell() 接口会导致权重丢失, 可以在训练前时使用 ms.save_checkpoint() 接口保存成ckpt文件, 重新组网时,使用 ms.load_checkpoint() 接口加载ckpt文件即可。 以下代码展示了如何加载数据集,进行10次模型训练,以及导出模型。 训练以及导出模型代码如下:

 1import mindspore as ms
 2from mindspore.train.serialization import export
 3from mindspore import nn, context
 4import numpy as np
 5from PIL import Image
 6import mindspore.dataset as ds
 7from mindspore.dataset import py_transforms
 8import mindspore.dataset.vision as CV
 9from mindspore.train.callback import  LossMonitor
10
11batch_size = 32
12img_size = (128, 128)
13data_path = './Target'
14
15# 数据预处理
16def rescale_to_0_1(image):
17    return image / 255.0
18
19# 自定义函数,添加 color 通道维度
20def add_channels(image):
21    if len(image.shape) == 2:  # 单个图像,没有 cin_channel 维度
22        image_four_channels = np.expand_dims(image, axis=0)
23    else:
24        pass
25        return image
26    return image_four_channels
27
28def export_cnn():
29    context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU")
30    resize_op = CV.Resize(img_size)
31    rescale_transform = ms.dataset.transforms.Compose([rescale_to_0_1])
32    f32_typecast = ms.dataset.transforms.TypeCast(ms.float32)
33    # 将读取的 RGB 转为 GRAY 模式
34    convert_gray = ms.dataset.vision.ConvertColor(ms.dataset.vision.ConvertMode.COLOR_RGB2GRAY)
35
36    transform = [convert_gray, resize_op, f32_typecast, rescale_transform, CV.HWC2CHW()]
37    train_data = ds.ImageFolderDataset(dataset_dir=data_path, decode=True, extensions=[".JPEG", ".PNG", ".JPG"])
38    train_data = train_data.map(input_columns="image", operations= transform)
39    train_data = train_data.map(operations=lambda image: add_channels(image), \
40                                    input_columns=["image"], \
41                                    output_columns=["image"])
42    train_data = train_data.batch(batch_size=batch_size)
43    data_iter = train_data.create_dict_iterator()
44    print("数据集加载完成")
45
46    my_model = CNN()
47    loss = nn.CrossEntropyLoss(reduction='mean')
48    optimizer = nn.Adam(my_model.trainable_params(), learning_rate=0.01)
49    cnn_model = ms.Model(my_model, loss_fn=loss, optimizer=optimizer, metrics={'Accuracy': nn.Accuracy()})
50    print("网络构建完成")
51    num_epoch = 10
52    cnn_model.train(num_epoch, train_data,callbacks=[LossMonitor()],dataset_sink_mode=False)
53    inputs = ms.Tensor(np.random.randn(1, 1, 128, 128).astype(np.float32))
54    ms.save_checkpoint(my_model, 'cnn.ckpt')
55    export(my_model, inputs, file_name='cnn', file_format="MINDIR")

3. 重新组网

在前面章节中,我们已经完成了AI的模型的训练和导出。 需要重新构建成一个新的网络结构,可以使用MindSpore框架的 nn.GraphCell() 接口来实现。 该部分内容包括加载训练好的cnn模型,灰度化处理,重新构建网络结构,并导出新的模型。 重新组网、导出模型以及测试的代码如下:

 1import cv2
 2import mindspore as ms
 3from mindspore.train.serialization import export
 4import numpy as np
 5from mindspore import nn, ops, context
 6from CNN import export_cnn
 7from GRAY import export_gray
 8from Connection import export_connection
 9
10class_names = ['BRDM_2', 'BTR_60', 'SLICY', 'T62', 'ZSU_23_4']
11class_labels = ["装甲侦察车", "装甲运输车", "不明", "坦克", "自行高炮"]
12class GrayCNN(nn.Cell):
13    def __init__(self):
14        super(GrayCNN, self).__init__()
15        self.split = ops.Split(axis=2, output_num=3)
16        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True)
17        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
18        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True)
19        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
20        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True)
21        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
22        self.flatten = nn.Flatten()
23        self.dense1 = nn.Dense(32768, 64)
24        self.dense2 = nn.Dense(64, 5)
25        self.relu = nn.ReLU()
26        self.cnn = nn.GraphCell(ms.load(file_name="cnn.mindir"))
27    def construct(self, x):
28        x = self.split(x)
29        b, g, r = x
30        x = (
31            b * 0.114 +
32            g * 0.587 +
33            r * 0.299
34        ).squeeze(-1)
35        x = ops.clip_by_value(x, 0, 255)
36        x = x / 255.0   # 数据归一化
37        x = x.reshape(1, 1, 128, 128)   # 灰度化处理后结果需要重塑成cnn输入形状
38        x = self.cnn(x)
39        return x
40
41if __name__ == "__main__":
42    export_cnn()
43    context.set_context(mode=context.GRAPH_MODE)
44    # 1. 读取图片(保持原始uint8类型)
45    color_image = cv2.imread("image_origin.jpg")  # 默认uint8
46    resized_image = cv2.resize(
47        color_image,
48        (128, 128),  # 目标尺寸(width, height)
49        interpolation=cv2.INTER_LINEAR
50    )
51    resized_image = resized_image.astype(np.float32)
52    resized_image_tensor = ms.Tensor(resized_image)
53
54    my_model = GrayCNN()
55    param_dict = ms.load_checkpoint("cnn.ckpt")
56    param_not_load, _ = ms.load_param_into_net(my_model, param_dict, strict_load=True)
57
58    input_tensor = ms.Tensor(np.ones((128, 128, 3), dtype=np.float32))
59    export(my_model, input_tensor, file_name='gray_cnn', file_format='MINDIR')
60
61    reload_cnn = nn.GraphCell(ms.load(file_name='gray_cnn.mindir'))
62    reload_cnn = ms.Model(reload_cnn)
63    m = reload_cnn.predict(resized_image_tensor)
64    print(m)
65    output_np = m.asnumpy()
66    if len(output_np.shape) == 2:  # 对于分类任务,通常输出是[batch_size, num_classes]
67        output_prob = np.exp(output_np) / np.exp(output_np).sum(axis=1, keepdims=True)
68        predicted_class = np.argmax(output_prob, axis=1)
69        print(f"预测结果: {class_labels[predicted_class[0]]}")

该代码首先定义了一个新的网络结构GrayCNN, 在 construct 方法中,首先通过gray模型处理输入图像,然后通过归一化和重塑张量,最后cnn模型进行识别。 在主函数中,首先导出cnn模型。 然后加载cnn模型参数,并使用MindSpore的 export 方法导出新的网络结构。 最后,使用重新组网后的模型进行预测,并输出结果。

4. 转换模型

在前面的代码中,我们已经使用了 export 方法导出了gray_cnn.mindir模型。 要将该模型部署到FT78NE平台,需要将MINDIR格式转换成mindspore lite的ms格式模型。 要将该模型转换为ms格式,可以使用MindSpore的转换工具(converter_lite)。 该工具已经集成在我们的 YHFT-IDE 中,可以直接在IDE中使用。或者也可以使用命令行工具进行转换。

转换命令如下:

./converter_lite --fmk=MINDIR --modelFile=gray_cnn.mindir --outputFile=gray_cnn

转换完成后,会在当前目录下生成gray_cnn.ms模型文件。该模型可以使用可视化工具(netron)可以打开该文件,查看模型结构。 该工具可以直接在 YHFT-IDE 中使用,可以从官网下载使用,也可以在线使用。在线地址为:https://netron.app/

该模型文件可视化如图所示:

gray_cnn模型结构图

5. 部署和运行程序

  • 在IDE中新建一个Python项目,将下面的 python完整代码示例 代码拷贝到项目中,并运行。 运行成功后,会在当前目录下生成一个名为 gray_cnn.midir 的模型文件,以及输出图片的预测结果。 结果如图所示:

    python运行结果

    将该文件转换为ms格式,并将ms格式模型和测试图片拷贝到FT78NE平台中。

  • 打开 YHFT-IDE ,新建工程。输入工程名、路径,工程类型选择 Heterogeneous , 输入交叉编译工具路径,然后点确定。会生成一个异构模板工程。通过修改 data_handler.cc 文件中的函数来调整输入输出数据, 输入改成读取的图片路径;输出改成相应的后处理。在 main 函数设置运行后端;修改 CMakeLists.txt 文件, 添加openCV库的lib和include路径, 最后编译该工程,编译成功后将build文件夹下的 main 拷贝到FT78NE平台中, 要和gray_cnn.ms模型同一个文件夹下。

    输入的c++代码示例如下:

 1#include <opencv2/opencv.hpp>
 2int readImage(float *reslut, std::string imagePath) {
 3    cv::Mat color_image = cv::imread(imagePath.c_str(), cv::IMREAD_COLOR);
 4    if (color_image.empty()) {
 5        fprintf(stderr, "read image failed\n");
 6        return -1;
 7    }
 8
 9    int width = 128;
10    int height = 128;
11    cv::Mat resized_image;
12    cv::resize(color_image, resized_image, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
13    resized_image.convertTo(resized_image, CV_32F);
14    const int channels = resized_image.channels();
15    const int element_count = width * height * channels;
16
17    for (int i = 0; i < height; ++i) {
18        const float *src_ptr = resized_image.ptr<float>(i);
19        float *dst_ptr = reslut + i * width * channels;
20        std::memcpy(dst_ptr, src_ptr, width * channels * sizeof(float));
21    }
22    return 0;
23}

设置后端代码如下:

1auto context = std::make_shared<mindspore::Context>();
2auto &device_list = context->MutableDeviceInfo();
3context->SetBuiltInDelegate(mindspore::DelegateMode::kPNNA);
4auto cpu_info = std::make_shared<mindspore::CPUDeviceInfo>();
5auto dsp_info = std::make_shared<mindspore::FT78NEDeviceInfo>();
6device_list.push_back(dsp_info);
7device_list.push_back(cpu_info);

输出结果后处理代码如下:

 1std::vector<float> floatArrayToVector(const float *array, size_t size) {
 2    std::vector<float> vec(array, array + size);
 3    return vec;
 4}
 5
 6std::vector<float> numpy_exp(const std::vector<float> &x) {
 7    std::vector<float> result;
 8    result.reserve(x.size());
 9    for (float num : x) {
10        result.push_back(exp(num));
11    }
12    return result;
13}
14
15float sum_rows(const std::vector<float> &vec, int axis) {
16    float sum = std::accumulate(vec.begin(), vec.end(), 0.0);
17    return sum;
18}
19
20std::vector<std::vector<float>> convertTo2D(const std::vector<float> &vec, int rows) {
21    std::vector<std::vector<float>> result;
22    if (vec.empty() || rows <= 0) {
23        return result;
24    }
25    int cols = (vec.size() + rows - 1) / rows;  // 计算列数
26    for (int i = 0; i < rows; ++i) {
27        std::vector<float> row;
28        for (int j = 0; j < cols; ++j) {
29        size_t index = i * cols + j;  // 计算索引
30        if (index < vec.size()) {
31            row.push_back(vec[index]);
32        } else {
33            row.push_back(0);  // 填充剩余空间,或者你可以选择不填充
34        }
35        }
36        result.push_back(row);
37    }
38    return result;
39}
40
41void GetOutputData(std::vector<MSTensor> &outputs) {
42    for (auto tensor : outputs) {
43        std::cout << "tensor name is:" << tensor.Name() << " tensor size is:" << tensor.DataSize()
44                << " tensor elements num is:" << tensor.ElementNum() << std::endl;
45        auto out_data = reinterpret_cast<const float *>(tensor.Data().get());
46        std::cout << "output data is:";
47        for (int i = 0; i < tensor.ElementNum() && i <= 50; i++) {
48        std::cout << out_data[i] << " ";
49        }
50        std::cout << std::endl;
51        std::vector<float> out = floatArrayToVector(out_data, tensor.ElementNum());
52        std::vector<float> exp_x = numpy_exp(out);
53        float sums = sum_rows(exp_x, 1);
54        std::vector<float> x1;
55        for (float num : exp_x) {
56            x1.push_back(num / sums);
57        }
58        std::vector<std::vector<float>> twoD = convertTo2D(x1, 1);
59        std::vector<int> argmax_indices = argmax(twoD);
60        std::vector<std::string> Predicted_class = {"装甲侦察车", "装甲运输车", "不明", "坦克", "自行高炮"};
61        std::cout << "预测结果: " << Predicted_class[argmax_indices[0]] << std::endl;
62    }
63}
  • 在FT78NE上运行可执行文件 main ,观察输出结果。与预期结果对比,验证模型推理的正确性。 执行命令如下:

    1./main gray_cnn.ms image_origin.jpg
    

执行结果如下图:

FT78NE运行结果

python完整代码示例

 1# CNN.py
 2import mindspore as ms
 3from mindspore.train.serialization import export
 4from mindspore import nn, context
 5import numpy as np
 6from PIL import Image
 7import mindspore.dataset as ds
 8from mindspore.dataset import py_transforms
 9import mindspore.dataset.vision as CV
10from mindspore.train.callback import  LossMonitor
11
12# # Define directories and parameters
13batch_size = 32
14img_size = (128, 128)
15data_path = '../cnn-sar/Target'
16
17# 数据预处理
18def rescale_to_0_1(image):
19    return image / 255.0
20
21# 自定义函数,添加 color 通道维度
22def add_channels(image):
23    if len(image.shape) == 2:  # 单个图像,没有 cin_channel 维度
24        # print("before ===> ", image.shape)
25        # 添加 color 通道维度,(128x128) => (1, 128, 128)
26        image_four_channels = np.expand_dims(image, axis=0)
27        # print("after  ===> ", image_four_channels.shape)
28    else:
29        pass
30        return image
31    return image_four_channels
32
33class CNN(ms.nn.Cell):
34    def __init__(self):
35        super().__init__()
36        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True)
37        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
38        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True)
39        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
40        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True)
41        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
42        self.flatten = nn.Flatten()
43        self.dense1 = nn.Dense(32768, 64)
44        self.dense2 = nn.Dense(64, 5)
45        self.relu = nn.ReLU()
46
47    def construct(self, x):
48        x = self.relu(self.conv1(x))
49        x = self.pool1(x)
50        x = self.relu(self.conv2(x))
51        x = self.pool2(x)
52        x = self.relu(self.conv3(x))
53        x = self.pool3(x)
54        x = self.flatten(x)
55        x = self.relu(self.dense1(x))
56        x = self.dense2(x)
57        return x
58
59def export_cnn():
60    context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU")
61    resize_op = CV.Resize(img_size)
62    rescale_transform = ms.dataset.transforms.Compose([rescale_to_0_1])
63    f32_typecast = ms.dataset.transforms.TypeCast(ms.float32)
64    # 将读取的 RGB 转为 GRAY 模式
65    convert_gray = ms.dataset.vision.ConvertColor(ms.dataset.vision.ConvertMode.COLOR_RGB2GRAY)
66
67    transform = [convert_gray, resize_op, f32_typecast, rescale_transform, CV.HWC2CHW()]
68    train_data = ds.ImageFolderDataset(dataset_dir=data_path, decode=True, extensions=[".JPEG", ".PNG", ".JPG"])
69    train_data = train_data.map(input_columns="image", operations= transform)
70    train_data = train_data.map(operations=lambda image: add_channels(image), \
71                                    input_columns=["image"], \
72                                    output_columns=["image"])
73    train_data = train_data.batch(batch_size=batch_size)
74    data_iter = train_data.create_dict_iterator()
75    print("数据集加载完成")
76    my_model = CNN()
77    loss = nn.CrossEntropyLoss(reduction='mean')
78    optimizer = nn.Adam(my_model.trainable_params(), learning_rate=0.01)
79    cnn_model = ms.Model(my_model, loss_fn=loss, optimizer=optimizer, metrics={'Accuracy': nn.Accuracy()})
80    print("网络构建完成")
81    num_epoch = 10
82    cnn_model.train(num_epoch, train_data,callbacks=[LossMonitor()],dataset_sink_mode=False)
83    inputs = ms.Tensor(np.random.randn(1, 1, 128, 128).astype(np.float32))
84    ms.save_checkpoint(my_model, 'cnn.ckpt')
85    export(my_model, inputs, file_name='cnn', file_format="MINDIR")
 1# GRAY_CNN.py
 2import cv2
 3import mindspore as ms
 4from mindspore.train.serialization import export
 5import numpy as np
 6from mindspore import nn, ops, context
 7from CNN import export_cnn
 8
 9class_names = ['BRDM_2', 'BTR_60', 'SLICY', 'T62', 'ZSU_23_4']
10class_labels = ["装甲侦察车", "装甲运输车", "不明", "坦克", "自行高炮"]
11
12class GrayCNN(nn.Cell):
13    def __init__(self):
14        super(GrayCNN, self).__init__()
15        self.split = ops.Split(axis=2, output_num=3)
16        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True)
17        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
18        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True)
19        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
20        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True)
21        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
22        self.flatten = nn.Flatten()
23        self.dense1 = nn.Dense(32768, 64)
24        self.dense2 = nn.Dense(64, 5)
25        self.relu = nn.ReLU()
26        self.cnn = nn.GraphCell(ms.load(file_name="cnn.mindir"))
27
28    def construct(self, x):
29        x = self.split(x)
30        b, g, r = x
31        x = (
32            b * 0.114 +
33            g * 0.587 +
34            r * 0.299
35        ).squeeze(-1)
36        x = ops.clip_by_value(x, 0, 255)
37        x = x / 255.0   # 数据归一化
38        x = x.reshape(1, 1, 128, 128)   # 灰度化处理后结果需要重塑成cnn输入形状
39        x = self.cnn(x)
40        return x
41
42if __name__ == "__main__":
43    export_cnn()
44    context.set_context(mode=context.GRAPH_MODE)
45    # 1. 读取图片(保持原始uint8类型)
46    color_image = cv2.imread("image_origin.jpg")  # 默认uint8
47    resized_image = cv2.resize(
48        color_image,
49        (128, 128),  # 目标尺寸(width, height)
50        interpolation=cv2.INTER_LINEAR
51    )
52    resized_image = resized_image.astype(np.float32)
53    resized_image_tensor = ms.Tensor(resized_image)
54    my_model = GrayCNN()
55    param_dict = ms.load_checkpoint("cnn.ckpt")
56    param_not_load, _ = ms.load_param_into_net(my_model, param_dict, strict_load=True)
57    input_tensor = ms.Tensor(np.ones((128, 128, 3), dtype=np.float32))
58    export(my_model, input_tensor, file_name='gray_cnn', file_format='MINDIR')
59    reload_cnn = nn.GraphCell(ms.load(file_name='gray_cnn.mindir'))
60    reload_cnn = ms.Model(reload_cnn)
61    m = reload_cnn.predict(resized_image_tensor)
62    print(m)
63    output_np = m.asnumpy()
64    if len(output_np.shape) == 2:  # 对于分类任务,通常输出是[batch_size, num_classes]
65        output_prob = np.exp(output_np) / np.exp(output_np).sum(axis=1, keepdims=True)
66        predicted_class = np.argmax(output_prob, axis=1)
67        print(f"预测结果: {class_labels[predicted_class[0]]}")